package com.iflytek.autofly.navisetting;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;

import com.iflytek.autofly.navisetting.NaviSetting.AidlStatus;
import com.iflytek.autofly.navisetting.NaviSetting.ErrorCode;
import com.iflytek.autofly.utils.FlyLog;
import com.mxnavi.naviserver.INaviService;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * <b>Description: </b>TODO<br/>
 * <b>Change History:</b><br/>
 * <table border="1">
 * <tr>
 * <td><b>user</b></td>
 * <td><b>date</b></td>
 * <td><b>time</b></td>
 * <td><b>reason</b></td>
 * </tr>
 * <tr>
 * <td>JunLuan@iflytek.com</td>
 * <td>2015/11/26</td>
 * <td>10:52</td>
 * <td>创建此文件</td>
 * </tr>
 * </table>
 *
 * @author JunLuan@iflytek.com
 */
public class MxNaviSetting implements INaviSetting {


    private static final String TAG = "MxNaviSetting";
    private static final int MSG_TODO_START = 0x1001;
    private static Context mContext = null;
    private static INaviSetting instance = null;
    private static LinkedBlockingQueue<NaviSetting.IBindListener> mListeners = new LinkedBlockingQueue<>();
    private INaviService mRemoteService = null;
    private volatile AidlStatus mState = AidlStatus.SERVICE_AIDL_NOT_BOUND;
    private BlockingQueue<Runnable> mTodoList = new LinkedBlockingQueue<>();
    private HandlerThread mTodoHandlerThread;
    private Handler mTodoHandler;

    //  private IDisplayServiceAIDLListener mServiceAIDLListener = null;
    private ServiceConnection mConnect = new ServiceConnection() {
        public void onServiceDisconnected(ComponentName name) {
            FlyLog.d(TAG, "enter|name=" + name);
            mRemoteService = null;

            synchronized (mState) {
                mState = AidlStatus.SERVICE_AIDL_NOT_BOUND;
                notifyClientServiceStatus(mState);
            }
            //mListeners.clear();
            //initClient();
        }

        public void onServiceConnected(ComponentName name, IBinder service) {
            FlyLog.d(TAG, "enter|name=" + name);
            mRemoteService = INaviService.Stub.asInterface(service);

            synchronized (mState) {
                mState = AidlStatus.SERVICE_AIDL_BOUND;
                notifyClientServiceStatus(mState);
                if (!mTodoList.isEmpty()) {
                    mTodoHandler.sendEmptyMessage(MSG_TODO_START);
                }
            }
        }
    };

    private MxNaviSetting() {
        mTodoHandlerThread = new HandlerThread("todo");
        mTodoHandlerThread.start();
        mTodoHandler = new Handler(mTodoHandlerThread.getLooper(), new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                if (msg.what == MSG_TODO_START) {
                    while (!mTodoList.isEmpty()) {
                        Runnable r = null;
                        try {
                            r = mTodoList.take();
                            r.run();
                        } catch (InterruptedException e) {
                            FlyLog.e(TAG, e);
                        }
                    }
                }
                return true;
            }
        });
    }

    public static INaviSetting registerListener(Context c, Object l) {
        synchronized (TAG) {
            mContext = c;
            if (instance == null) {
                instance = new MxNaviSetting();
            }
            if (!mListeners.contains(l)) {
                mListeners.add((NaviSetting.IBindListener) l);
            } else {
                FlyLog.w(TAG, "has register listener");
            }
            if (((MxNaviSetting) instance).serviceCheck() == ErrorCode.ERROR_OK) {
                // already bound, just notify
                ((NaviSetting.IBindListener) l).onServiceStatusChanged(AidlStatus.SERVICE_AIDL_BOUND);
            }
            return instance;
        }
    }

    public static INaviSetting getInstance(Context context) throws RemoteException {
        mContext = context;
        if (instance == null) {
            instance = new MxNaviSetting();
        }

        ((MxNaviSetting) instance).serviceCheck();
        return instance;
    }

    public static void unregisterListener(Context applicationContext, Object l) {
        if (mListeners.contains(l)) {
            mListeners.remove(l);
        } else {
            FlyLog.w(TAG, "not register yet");
        }
    }

    private void notifyClientServiceStatus(AidlStatus status) {
        for (NaviSetting.IBindListener listener : mListeners) {
            listener.onServiceStatusChanged(status);
        }
    }

    private int initClient() {
        FlyLog.d(TAG, "enter");
        Intent intent = new Intent();
        intent.setAction("com.mxnavi.mxnavicar.service.MxNaviCarService");
        boolean ret = mContext.bindService(intent, this.mConnect, Context.BIND_AUTO_CREATE);
        if (ret) {
            FlyLog.d(TAG, "bindService OK");
        } else {
            FlyLog.e(TAG, "bindService failed");
            synchronized (mState) {
                mState = AidlStatus.SERVICE_AIDL_NOT_BOUND;
                notifyClientServiceStatus(mState);
            }
        }

        return ret ? ErrorCode.ERROR_OK : ErrorCode.ERROR_AIDL_NOT_BOUND;
    }

    private int serviceCheck() {
        synchronized (mState) {
            FlyLog.d(TAG, "enter|state=" + mState);
            switch (mState) {
                case SERVICE_AIDL_NOT_BOUND:
                    initClient();
                    FlyLog.e(TAG, "SERVICE_AIDL_NOT_BOUND");
                    return ErrorCode.ERROR_AIDL_NOT_BOUND;
                case SERVICE_AIDL_BINDING:
                    FlyLog.e(TAG, "SERVICE_AIDL_BINDING");
                    return ErrorCode.ERROR_AIDL_BINDING;
                case SERVICE_AIDL_BOUND:
                    return ErrorCode.ERROR_OK;
            }
        }
        FlyLog.e(TAG, "ERROR_NOT_SUPPORT");
        return ErrorCode.ERROR_NOT_SUPPORT;
    }

    @Override
    public boolean getContentPlayState() {
        if (serviceCheck() == ErrorCode.ERROR_OK) {
            try {
                return !mRemoteService.isMute();
            } catch (RemoteException e) {
                FlyLog.e(TAG, e);
            }
        }
        return false;
    }

    @Override
    public void setContentPlayState(final boolean play) {
        int checkResult = serviceCheck();
        if (checkResult == ErrorCode.ERROR_OK) {
            try {
                mRemoteService.setMute(!play);
            } catch (RemoteException e) {
                FlyLog.e(TAG, e);
            }
        } else {
            penddingTodo(new Runnable() {
                @Override
                public void run() {
                    setContentPlayState(play);
                }
            });
        }
    }

    @Override
    public NaviSetting.PlayedContentLevel getPlayedContentLevel() {
        if (serviceCheck() == ErrorCode.ERROR_OK) {
            NaviSetting.PlayedContentLevel level = null;
            try {
                switch (mRemoteService.getVoiceBroadcastLevel()) {
                    case 0:
                        level = NaviSetting.PlayedContentLevel.SIMPLE;
                        break;
                    case 1:
                        level = NaviSetting.PlayedContentLevel.STANDARD;
                        break;
                    case 2:
                        level = NaviSetting.PlayedContentLevel.FULL;
                        break;
                }
                return level;
            } catch (RemoteException e) {
                FlyLog.e(TAG, e);
            }
        } else {


        }
        return null;
    }

    @Override
    public void setPlayedContentLevel(final NaviSetting.PlayedContentLevel lvl) {
        if (serviceCheck() == ErrorCode.ERROR_OK) {
            int level = 0;
            try {
                switch (lvl) {
                    case SIMPLE:
                        level = 0;
                        break;
                    case STANDARD:
                        level = 1;
                        break;
                    case FULL:
                        level = 2;
                        break;
                }
                mRemoteService.setVoiceBroadcastLevel(level);
            } catch (RemoteException e) {
                FlyLog.e(TAG, e);
            }
        } else {
            penddingTodo(new Runnable() {
                @Override
                public void run() {
                    setPlayedContentLevel(lvl);
                }
            });
        }
    }

    @Override
    public NaviSetting.DayNightMode getDayNightMode() {
        FlyLog.d(TAG, "enter");
        if (serviceCheck() == ErrorCode.ERROR_OK) {
            int m = 0;
            NaviSetting.DayNightMode mode = null;
            try {
                String modePara = mRemoteService.getViewColorMode();
                JSONObject jsonObject = new JSONObject(modePara);
                m = jsonObject.getInt("enColorMode");
            } catch (RemoteException e) {
                FlyLog.e(TAG, e);
            } catch (JSONException e) {
                FlyLog.e(TAG, e);
            }
            switch (m) {
                case 1:
                    mode = NaviSetting.DayNightMode.DAY;
                    break;
                case 2:
                    mode = NaviSetting.DayNightMode.NIGHT;
                    break;
                case 3:
                    mode = NaviSetting.DayNightMode.AUTO;
                    break;
                default:
                    FlyLog.e(TAG, "unknown mode " + m);
            }
            return mode;
        }
        FlyLog.e(TAG, "unexpected");
        return null;
    }

    @Override
    public void setDayNightMode(final NaviSetting.DayNightMode mode) {
        if (serviceCheck() == ErrorCode.ERROR_OK) {
            JSONObject jsonObject = new JSONObject();
            switch (mode) {
                case DAY:
                    try {
                        jsonObject.put("enColorMode", 1);
                    } catch (JSONException e) {
                        FlyLog.e(TAG, e);
                    }
                    break;
                case NIGHT:
                    try {
                        jsonObject.put("enColorMode", 2);
                    } catch (JSONException e) {
                        FlyLog.e(TAG, e);
                    }
                    break;
                // TODO need modify according brightness
                case AUTO:
                    try {
                        jsonObject.put("enColorMode", 3);
                    } catch (JSONException e) {
                        FlyLog.e(TAG, e);
                    }
                    break;
            }
            try {
                mRemoteService.setViewColorMode(jsonObject.toString());
            } catch (RemoteException e) {
                FlyLog.e(TAG, e);
            }
        } else {
            penddingTodo(new Runnable() {
                @Override
                public void run() {
                    setDayNightMode(mode);
                }
            });
        }
    }

    @Override
    public NaviSetting.NaviPathMode getNaviPathMode() {
        if (serviceCheck() == ErrorCode.ERROR_OK) {
            NaviSetting.NaviPathMode mode = null;
            try {
                int condition = mRemoteService.getDefaultClacCondition();
                switch (condition) {
                    case 0:
                        mode = NaviSetting.NaviPathMode.SUGGEST;
                        break;
                    case 1:
                        mode = NaviSetting.NaviPathMode.HIGHWAY;
                        break;
                    case 2:
                        mode = NaviSetting.NaviPathMode.DISTANCE;
                        break;
                    case 3:
                        mode = NaviSetting.NaviPathMode.ECONOMIC;
                        break;
                    default:
                        FlyLog.e(TAG, "unknown " + condition);
                }
                return mode;
            } catch (RemoteException e) {
                FlyLog.e(TAG, e);
            }
        }
        FlyLog.e(TAG, "unexpected");
        return null;
    }

    @Override
    public void setNaviPathMode(final NaviSetting.NaviPathMode mode) {
        if (serviceCheck() == ErrorCode.ERROR_OK) {
            int condition = 0;
            switch (mode) {
                case SUGGEST:
                    condition = 0;
                    break;
                case HIGHWAY:
                    condition = 1;
                    break;
                case DISTANCE:
                    condition = 2;
                    break;
                case ECONOMIC:
                    condition = 3;
                    break;
            }
            try {
                mRemoteService.setDefaultClacCondition(condition);
            } catch (RemoteException e) {
                FlyLog.e(TAG, e);
            }
        } else {
            penddingTodo(new Runnable() {
                @Override
                public void run() {
                    setNaviPathMode(mode);
                }
            });
        }
    }

    // 未绑定，serviceCheck会绑定
    // 但是需要将本次操作加入todolist
    // 在服务绑定完毕后，执行todolist里的操作
    private void penddingTodo(Runnable runnable) {
        mTodoList.add(runnable);
    }

    @Override
    public int getSpeaker() {
        if (serviceCheck() == ErrorCode.ERROR_OK) {
            try {
                return mRemoteService.getNaviPronunciationType();
            } catch (RemoteException e) {
                FlyLog.e(TAG, e);
            }
        }
        return 0;
    }

    @Override
    public void setSpeaker(final int id) {
        if (serviceCheck() == ErrorCode.ERROR_OK) {
            try {
                mRemoteService.setNaviPronunciationType(id);
            } catch (RemoteException e) {
                FlyLog.e(TAG, e);
            }
        } else {
            penddingTodo(new Runnable() {
                @Override
                public void run() {
                    setSpeaker(id);
                }
            });
        }
    }

    @Override
    public void register(Context c, Object l) {

    }

    @Override
    public void unregister(Context c) {

    }

    @Override
    public void init(Context c) {

    }

    @Override
    public void fini(Context c) {
        synchronized (mState) {
            if (mState == AidlStatus.SERVICE_AIDL_BOUND) {
                c.unbindService(mConnect);
            }
            mState = AidlStatus.SERVICE_AIDL_NOT_BOUND;
            notifyClientServiceStatus(mState);
        }
        mListeners.clear();
    }

    @Override
    public String getNaviVersion() {
        if (serviceCheck() == ErrorCode.ERROR_OK) {
            try {
                return mRemoteService.getMxNaviSoftVersion();
            } catch (RemoteException e) {
                FlyLog.e(TAG, e);
            }
        }
        return "unknown";
    }

    @Override
    public String getNaviDataVersion() {
        if (serviceCheck() == ErrorCode.ERROR_OK) {
            try {
                String versionMX = mRemoteService.getMxNaviDataVersion();
//               String versionUTF8 = new String(versionMX.getBytes("GBK"), "UTF8");
                FlyLog.d(TAG, versionMX);
                return versionMX;
//                return versionUTF8;
            } catch (RemoteException e) {
                FlyLog.e(TAG, e);
//            } catch (UnsupportedEncodingException e) {
//                FlyLog.e(TAG, e);
            }
        }
        return "unknown";
    }

}
